package com.xzg.erp.product.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xzg.erp.common.BusinessException;
import com.xzg.erp.common.Constants;
import com.xzg.erp.product.entity.*;
import com.xzg.erp.product.entity.dto.ProductDto;
import com.xzg.erp.product.mapper.ProductMapper;
import com.xzg.erp.product.service.IProductService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xzg.erp.product.service.IProductTagService;
import com.xzg.erp.product.service.IProductTypeService;
import com.xzg.erp.system.entity.Attachment;
import com.xzg.erp.system.service.IAttachmentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 产品表 服务实现类
 * </p>
 *
 * @author qwc
 * @since 2025-02-12
 */
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements IProductService {

    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private IProductTypeService productTypeService;
    @Autowired
    private IProductTagService productTagService;
    @Autowired
    private IAttachmentService attachmentService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveProduct(ProductDto dto) {
        int userId = StpUtil.getLoginIdAsInt();
        LocalDateTime now = LocalDateTime.now();
        Product product = objectMapper.convertValue(dto, Product.class);
        product.setCreatedBy(userId);
        product.setCreatedAt(now);
        product.setUpdatedBy(userId);
        product.setUpdatedAt(now);
        // 保存产品
        this.save(product);
        // 保存产品关联标签
        saveTags(dto, now, userId);
        // 保存产品关联类型
        saveTypes(dto, now, userId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void logicDeleteByIds(List<Integer> ids) {
        int userId = StpUtil.getLoginIdAsInt();
        LocalDateTime now = LocalDateTime.now();
        List<Product> list = this.list(Wrappers.<Product>lambdaQuery().in(Product::getId, ids));
        list.forEach(product -> {
            product.setDeleted(product.getId());
            product.setUpdatedAt(now);
            product.setUpdatedBy(userId);
        });
        this.updateBatchById(list);
        deleteTags(ids, userId, now);
        deleteTypes(ids, userId, now);
        deleteFile(ids, userId, now);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateProduct(ProductDto dto) {
        int userId = StpUtil.getLoginIdAsInt();
        LocalDateTime now = LocalDateTime.now();
        Product product = this.getById(dto.getId());
        updateProductByDto(product, dto);
        product.setUpdatedBy(userId);
        product.setUpdatedAt(now);
        this.updateById(product);
        List<Integer> ids = Arrays.asList(dto.getId());
        deleteTags(ids, userId, now);
        deleteTypes(ids, userId, now);
        saveTags(dto, now, userId);
        saveTypes(dto, now, userId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Set<String>> importProduct(List<Product> replace, List<Product> notReplace) {
        Map<String, Set<String>> map = new HashMap<>();
        if (CollUtil.isNotEmpty(replace)) {
            Set<String> codes = replaceProduct(replace);
            if (CollUtil.isNotEmpty(codes)) {
                map.put(Constants.REPLACE, codes);
            }
        }
        if (CollUtil.isNotEmpty(notReplace)) {
            Set<String> codes = notReplaceProduct(notReplace);
            if (CollUtil.isNotEmpty(codes)) {
                map.put(Constants.NOT_REPLACE, codes);
            }
        }
        return map;
    }

    private Set<String> replaceProduct(List<Product> replace) {
        List<Product> exist = this.list(Wrappers.<Product>lambdaQuery()
                .in(Product::getCode, replace.stream().map(Product::getCode).collect(Collectors.toList()))
                .eq(Product::getDeleted, Constants.UN_DELETED));
        Set<String> existSet = new HashSet<>();
        existSet.addAll(exist.stream().map(Product::getCode).collect(Collectors.toSet()));
        Map<Boolean, List<Product>> ifSave
                = replace.stream().collect(Collectors.partitioningBy(p -> existSet.contains(p.getCode())));
        List<Product> save = ifSave.get(Boolean.FALSE);
        if (CollUtil.isNotEmpty(save)) this.saveBatch(save);
        List<Product> replace2 = ifSave.get(Boolean.TRUE);
        if (CollUtil.isNotEmpty(replace2)) {
            replaceExist(exist, replace2);
            this.updateBatchById(exist);
        }
        return existSet;
    }

    private void replaceExist(List<Product> exist, List<Product> replace) {
        Map<String, Product> pMap = replace.stream().collect(Collectors.toMap(Product::getCode, p -> p, (p1, p2) -> p2));
        for (Product product : exist) {
            Product r = pMap.get(product.getCode());
            if (Objects.nonNull(r)) {
                product.setName(r.getName());
                product.setPrice(r.getPrice());
                product.setRemark(r.getRemark());
                product.setSpecification(r.getSpecification());
                product.setUpdatedAt(r.getCreatedAt());
                product.setUpdatedBy(r.getCreatedBy());
            }

        }
    }

    private Set<String> notReplaceProduct(List<Product> notRreplace) {
        List<Product> exist = this.list(Wrappers.<Product>lambdaQuery()
                .in(Product::getCode, notRreplace.stream().map(Product::getCode).collect(Collectors.toList()))
                .eq(Product::getDeleted, Constants.UN_DELETED));
        Set<String> existSet = new HashSet<>();
        existSet.addAll(exist.stream().map(Product::getCode).collect(Collectors.toSet()));
        List<Product> save = notRreplace.stream().filter(p -> !existSet.contains(p.getCode())).collect(Collectors.toList());
        this.saveBatch(save);
        return existSet;
    }

    private void updateProductByDto(Product product, ProductDto dto) {
        product.setName(dto.getName());
        Product one = this.getOne(Wrappers.<Product>lambdaQuery().eq(Product::getCode, dto.getCode()).eq(Product::getDeleted, Constants.UN_DELETED));
        if (Objects.nonNull(one) && !one.getId().equals(dto.getId())) {
            throw new BusinessException(HttpStatus.CONFLICT, Constants.DATA_EXIT);
        }
        product.setCode(dto.getCode());
        product.setPrice(dto.getPrice());
        product.setRemark(dto.getRemark());
        product.setSpecification(dto.getSpecification());
    }

    private void deleteTypes(List<Integer> ids, int userId, LocalDateTime now) {
        List<ProductTypeProduct> pTypes = this.getBaseMapper().selectTypesByProductId(ids);
        if (CollUtil.isNotEmpty(pTypes)) {
            pTypes.forEach(type -> {
                type.setDeleted(type.getId());
                type.setUpdatedBy(userId);
                type.setUpdatedAt(now);
            });
            this.getBaseMapper().deleteTypesIds(pTypes);
        }
    }

    private void deleteTags(List<Integer> ids, int userId, LocalDateTime now) {
        List<ProductTagProduct> pTags = this.getBaseMapper().selectTagsByProductId(ids);
        if (CollUtil.isNotEmpty(pTags)) {
            pTags.forEach(tag -> {
                tag.setDeleted(tag.getId());
                tag.setUpdatedBy(userId);
                tag.setUpdatedAt(now);
            });
            this.getBaseMapper().deleteTagsByIds(pTags);
        }

    }

    private void saveTypes(ProductDto dto, LocalDateTime now, int userId) {
        List<Integer> typeIds = dto.getTypeId();
        if (CollUtil.isNotEmpty(typeIds)) {
            List<ProductType> types = productTypeService.listByIds(typeIds);
            List<ProductTypeProduct> pTypes = types.stream().map(type -> {
                Integer typeId = type.getId();
                ProductTypeProduct productTypeProduct = new ProductTypeProduct();
                productTypeProduct.setProductId(dto.getId());
                productTypeProduct.setTypeId(typeId);
                productTypeProduct.setCreatedAt(now);
                productTypeProduct.setCreatedBy(userId);
                return productTypeProduct;
            }).collect(Collectors.toList());
            this.getBaseMapper().saveType(pTypes);
        }
    }

    private void saveTags(ProductDto dto, LocalDateTime now, int userId) {
        List<Integer> tagIds = dto.getTagId();
        if (CollUtil.isNotEmpty(tagIds)) {
            List<ProductTag> tags = productTagService.listByIds(tagIds);
            List<ProductTagProduct> pTags = tags.stream().map(tag -> {
                Integer tagId = tag.getId();
                ProductTagProduct productTagProduct = new ProductTagProduct();
                productTagProduct.setProductId(dto.getId());
                productTagProduct.setTagId(tagId);
                productTagProduct.setCreatedAt(now);
                productTagProduct.setCreatedBy(userId);
                return productTagProduct;
            }).collect(Collectors.toList());
            this.getBaseMapper().saveTag(pTags);
        }
    }


    private void deleteFile(List<Integer> ids, int userId, LocalDateTime now) {
        List<Attachment> list = attachmentService.list(Wrappers.<Attachment>lambdaQuery()
                .eq(Attachment::getRelatedTable, Constants.TABLE_NAME)
                .in(Attachment::getRelatedTablePrimaryKey, ids));
        if (CollUtil.isNotEmpty(list)) {
            list.forEach(file -> {
                file.setDeleted(file.getId());
                file.setUpdatedBy(userId);
                file.setUpdatedAt(now);
            });
            attachmentService.saveBatch(list);
        }
    }
}
